#pragma once

#include <utils/Concepts.h>
#include <utils/HasValue.h>
#include <utils/Map.h>
#include <utils/StringUtils.h>
#include <utils/config.h>

#include <boost/describe.hpp>
#include <boost/mp11.hpp>

#include <any>
#include <cstddef>
#include <memory>
#include <stdexcept>
#include <string>

namespace solar::internal
{
    template <std::size_t N, typename T>
        requires(N <= 0)
    void getParaAsAnyImpl(std::size_t /*index*/, T* /*object*/, std::any& /*result*/)
    {
        throw std::invalid_argument("Wrong index");
    }

    template <std::size_t N, typename T>
    void getParaAsAnyImpl(std::size_t index, T* object, std::any& result)
    {
        if (index == N - 1)
        {
            using D1 = boost::describe::describe_members<std::remove_reference_t<T>,
                                                         boost::describe::mod_private>;
            using V1 = boost::mp11::mp_at_c<D1, N - 1>;
            result = *object.*V1::pointer;
            return;
        }
        getParaAsAnyImpl<N - 1>(index, object, result);
    }

    template <std::size_t N, typename T> auto getParaAsAny(std::size_t index, T* object) -> std::any
    {
        std::any result;
        getParaAsAnyImpl<N>(index, object, result);
        return result;
    }

    template <std::size_t N, typename T>
        requires(N <= 0)
    void setParaImpl(std::size_t /*index*/, const std::any& /*value*/, T* /*object*/)
    {
        throw std::invalid_argument("Wrong index");
    }

    template <std::size_t N, typename T>
    void setParaImpl(std::size_t index, const std::any& value, T* object)
    {
        if (index == N - 1)
        {
            using D1 = boost::describe::describe_members<std::remove_reference_t<T>,
                                                         boost::describe::mod_private>;
            using V1 = boost::mp11::mp_at_c<D1, N - 1>;
            *object.*V1::pointer =
                std::any_cast<std::remove_reference_t<decltype(*object.*V1::pointer)>>(value);
            return;
        }
        setParaImpl<N - 1>(index, value, object);
    }

    template <std::size_t N, typename T>
    void setPara(std::size_t index, const std::any& value, T* object)
    {
        setParaImpl<N>(index, value, object);
    }

    template <std::size_t N, typename T>
        requires(N <= 0)
    void getParaPtrImpl(std::size_t /*index*/, T* /*object*/, std::any& /*result*/)
    {
        throw std::invalid_argument("Wrong index");
    }

    template <std::size_t N, typename T>
    void getParaPtrImpl(std::size_t index, T* object, std::any& result)
    {
        if (index == N - 1)
        {
            using D1 = boost::describe::describe_members<std::remove_reference_t<T>,
                                                         boost::describe::mod_private>;
            using V1 = boost::mp11::mp_at_c<D1, N - 1>;
            result = std::addressof(*object.*V1::pointer);
            return;
        }
        getParaPtrImpl<N - 1>(index, object, result);
    }

    template <std::size_t N, typename T> auto getParaPtr(std::size_t index, T* object) -> std::any
    {
        std::any result;
        getParaPtrImpl<N>(index, object, result);
        return result;
    }

    template <std::size_t N, typename T>
        requires(N <= 0)
    void getParaAsStringImpl(std::size_t /*index*/, const T* const /*object*/,
                             std::string& /*result*/)
    {
        throw std::invalid_argument("Wrong index");
    }

    template <std::size_t N, typename T>
    void getParaAsStringImpl(std::size_t index, const T* const object, std::string& result)
    {
        if (index == N - 1)
        {
            using D1 = boost::describe::describe_members<std::remove_reference_t<T>,
                                                         boost::describe::mod_private>;
            using V1 = boost::mp11::mp_at_c<D1, N - 1>;
            if constexpr (ToStringConvertible<
                              std::remove_reference_t<decltype(*object.*V1::pointer)>>)
            {
                result = ::solar::toString(*object.*V1::pointer);
            }
            return;
        }
        getParaAsStringImpl<N - 1>(index, object, result);
    }

    template <std::size_t N, typename T>
    auto getParaAsString(std::size_t index, const T* const object) -> std::string
    {
        std::string result;
        getParaAsStringImpl<N>(index, object, result);
        return result;
    }

    template <std::size_t N, typename T>
        requires(N <= 0)
    void setParaFromStringImpl(std::size_t /*index*/, const std::string& /*value*/, T* /*object*/)
    {
        throw std::invalid_argument("Wrong index");
    }

    template <std::size_t N, typename T>
    void setParaFromStringImpl(std::size_t index, const std::string& value, T* object)
    {
        if (index == N - 1)
        {
            using D1 = boost::describe::describe_members<std::remove_reference_t<T>,
                                                         boost::describe::mod_private>;
            using V1 = boost::mp11::mp_at_c<D1, N - 1>;
            if constexpr (FromStringConvertible<
                              std::remove_reference_t<decltype(*object.*V1::pointer)>>)
            {
                *object.*V1::pointer =
                    ::solar::fromString<std::remove_reference_t<decltype(*object.*V1::pointer)>>(
                        value);
            }
            else
            {
                throw std::logic_error("Unable to set parameter from string");
            }
            return;
        }
        setParaFromStringImpl<N - 1>(index, value, object);
    }

    template <std::size_t N, typename T>
    void setParaFromString(std::size_t index, const std::string& value, T* object)
    {
        setParaFromStringImpl<N>(index, value, object);
    }

    template <std::size_t N, typename T>
        requires(N <= 0)
    void paraHasValueImpl(std::size_t /*index*/, const T* const /*object*/, bool& /*result*/)
    {
        throw std::invalid_argument("Wrong index");
    }

    template <std::size_t N, typename T>
    void paraHasValueImpl(std::size_t index, const T* const object, bool& result)
    {
        if (index == N - 1)
        {
            using D1 = boost::describe::describe_members<std::remove_reference_t<T>,
                                                         boost::describe::mod_private>;
            using V1 = boost::mp11::mp_at_c<D1, N - 1>;
            result = ::solar::hasValue(*object.*V1::pointer);
            return;
        }
        paraHasValueImpl<N - 1>(index, object, result);
    }

    template <std::size_t N, typename T>
    auto paraHasValue(std::size_t index, const T* const object) -> bool
    {
        bool result = false;
        paraHasValueImpl<N>(index, object, result);
        return result;
    }
} // namespace solar::internal
